package pers.muci.moudles.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import pers.muci.moudles.sevice.UserService;

import javax.sql.DataSource;

/**
 * ClassName: SecurityConfig
 * Description: 配置类
 * date: 2021/9/30 10:30
 *
 * @author muci
 * @since JDK 1.8
 */
@Configuration
@EnableGlobalMethodSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserService userService;

    @Autowired
    DataSource dataSource;

    /**
     * AuthenticationManagerBuilder 类顶层父类是 SecurityBuilder,父类有：ProviderManagerBuilder
     * SecurityBuilder类: 用于构建对象，build()
     * ProviderManagerBuilder类：用于操作 创建ProviderManager的SecurityBuilder 的接口
     * AuthenticationManagerBuilder类， 用于创建 AuthenticationManager（验证管理器）
     * 使用它可以构建 构建内存身份验证、LDAP 身份验证、基于 JDBC 的身份验证，以及添加 AuthenticationProvider类
     * AuthenticationManager：验证管理器，顶级接口，用于处理  Authentication 请求
     * Authentication：存储验证对象信息，
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //基于内置数据库 jdbcAuthentication
        /*auth
                .jdbcAuthentication()
                .dataSource(dataSource)
                .usersByUsernameQuery("select id,account_non_expired,account_non_locked,credentials_non_expired,enabled,password,username from t_user where username = ?")
                .authoritiesByUsernameQuery("select r.id, name, name_zh  from t_role r  where r.id in (select ur.roles_id from t_user_roles ur where t_user_id = (select u.id from t_user u where u.username = ?))")
                .passwordEncoder(NoOpPasswordEncoder.getInstance());*/
        //使用 UserDetailsService
        auth.userDetailsService(userService);
    }

    /**
     * 设置基于内存的登陆用户
     * 重写 configure(AuthenticationManagerBuilder auth) 后，这种方式失效
     *
     * @return
     */
    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("muci").password("222").roles("admin").build());
        return manager;
    }


    @Override
    public void configure(WebSecurity web) throws Exception {
        //配置需要忽略的URL地址，一般是静态文件
        web.ignoring().antMatchers("/static/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
//                配置授权请求
                .authorizeRequests()
                //指定 哪些路径 需要什么权限
                .antMatchers("/m/admin/**").hasRole("admin")
                .antMatchers("/m/user/**").hasRole("user")
                //所有请求
                .anyRequest()
                //需要验证
                .authenticated()
                .and()
//                表单配置
                .formLogin()
                //配置登陆ye，若loginProcessingUrl未配置，则登陆接口也是这个
                .loginPage("/m/login/toLogin")
                //配置登陆接口
                .loginProcessingUrl("/m/login/doLogin")
                //配置登陆表单传参属性
                .passwordParameter("password")
                .usernameParameter("username")
                //登陆成功回调 defaultSuccessUrl  successForwardUrl 只需要配置一个
                .defaultSuccessUrl("/m/main/toMain", true)
                //失败回调 failureForwardUrl  failureUrl
                .failureForwardUrl("/m/login/toLogin")
                .permitAll()
                .and()
//                注销登陆
                .logout()
                //设置 注销登陆的 url
                // .logoutUrl("/m/login/logout")
                // 默认GET方式，此处设置为 POST方式，logoutUrl  和 它只设置一个即可
                .logoutRequestMatcher(new AntPathRequestMatcher("/m/login/logout", "GET"))
                // 注销成功后跳转的界面
                .logoutSuccessUrl("/m/login/toLogin")
                //删除cookies
                .deleteCookies()
                //清除认证信息， 默认清除
                .clearAuthentication(true)
                //清除session， 默认会清除
                .invalidateHttpSession(true)
                //请求允许
                .permitAll()
                .and()
//                记住我，自动登陆
                .rememberMe()
                .key("muci")
                .and()
                .csrf().disable()
//                设置账号登陆数量，非前后端
                .sessionManagement()
                //最大会话数量1， 默认会自动踢掉旧的登陆
                .maximumSessions(1)
                //开启最大会话阻止登陆，不能踢掉就登陆
                .maxSessionsPreventsLogin(true);

    }

    /**
     * PasswordEncoder：security 中用于密码加密的接口
     * spring boot 默认使 BCryptPasswordEncoder 密码加密方式
     * 此处是自定义密码加密为 不加密
     *
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * 授权操作-角色继承
     *
     * @return
     */
    @Bean
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_admin > ROLE_user");
        return roleHierarchy;
    }

}
